-
Notifications
You must be signed in to change notification settings - Fork 8
Support Jacobi matrix computation via Cholesky and QR #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #120 +/- ##
==========================================
+ Coverage 88.74% 89.70% +0.96%
==========================================
Files 16 17 +1
Lines 1652 1749 +97
==========================================
+ Hits 1466 1569 +103
+ Misses 186 180 -6
... and 1 file with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
ok @dlfivefifty, this should be better. let me know if you spot anything else. Tests are passing and I am pretty much happy with it as is for now. I think we also want to implement sqrtweighted but I would rather do that as a separate PR once this is merged |
*) split storage 2xN matrix into 2 vectors. *) remove an instance of Float64 hardcoding *) remove an unnecessary infinite tail of zeros *) pre-fill cached arrays to avoid step-by-step expansion
@dlfivefifty: Another round of minor improvements, with band storage now styled after the band storage in SemiclassicalOPs, i.e. like in https://github.com/JuliaApproximation/SemiclassicalOrthogonalPolynomials.jl/blob/5e4d53d5d875d54a8b459839237a7c537d9d859d/src/SemiclassicalOrthogonalPolynomials.jl#L110 Also some minor typing improvements here and there. Not sure how to share profiler results in a sensible manner here since I mostly rely on ProfileView.jl, but here are some benchmarks on my laptop: julia> @benchmark qr_jacobimatrix(sqrtW, P, false)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 16.051 μs … 8.576 ms ┊ GC (min … max): 0.00% … 99.45%
Time (median): 17.477 μs ┊ GC (median): 0.00%
Time (mean ± σ): 21.229 μs ± 136.794 μs ┊ GC (mean ± σ): 11.10% ± 1.72%
▄█▆▃
▁▅████▇▅▄▃▃▂▃▂▃▃▃▃▃▃▃▃▃▃▃▃▃▂▂▂▂▂▂▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
16.1 μs Histogram: frequency by time 29.5 μs <
Memory estimate: 28.17 KiB, allocs estimate: 296. julia> @benchmark qr_jacobimatrix(sqrtW, P, false)[1000,1000]
BenchmarkTools.Trial: 3487 samples with 1 evaluation.
Range (min … max): 1.212 ms … 10.888 ms ┊ GC (min … max): 0.00% … 84.50%
Time (median): 1.328 ms ┊ GC (median): 0.00%
Time (mean ± σ): 1.430 ms ± 884.072 μs ┊ GC (mean ± σ): 6.12% ± 8.55%
▃▃ ▂▁▁█▅▂▅▁
▂▂▂▂▄███████████▅▅▅▅▅▄▃▃▄▄▄▄▃▃▃▂▃▃▂▂▂▂▂▂▂▁▂▁▁▁▂▁▁▁▁▁▁▁▁▁▁▂▂ ▃
1.21 ms Histogram: frequency by time 1.77 ms <
Memory estimate: 914.77 KiB, allocs estimate: 11400. Compared with Lanczos for the same problem: julia> @benchmark jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1))))
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
Range (min … max): 290.774 μs … 15.843 ms ┊ GC (min … max): 0.00% … 90.28%
Time (median): 311.731 μs ┊ GC (median): 0.00%
Time (mean ± σ): 331.059 μs ± 409.251 μs ┊ GC (mean ± σ): 3.26% ± 2.60%
▂▆███▅▅▄▂▂
▁▁▃▆███████████▇▆▅▅▅▄▄▄▃▃▃▃▃▃▃▃▂▂▃▃▂▂▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▃
291 μs Histogram: frequency by time 404 μs <
Memory estimate: 97.06 KiB, allocs estimate: 833. julia> @benchmark jacobimatrix(LanczosPolynomial(@.(wf.(x)),Normalized(legendre(0..1))))[1000,1000]
BenchmarkTools.Trial: 3 samples with 1 evaluation.
Range (min … max): 1.907 s … 3.121 s ┊ GC (min … max): 1.39% … 1.56%
Time (median): 2.104 s ┊ GC (median): 2.02%
Time (mean ± σ): 2.377 s ± 651.585 ms ┊ GC (mean ± σ): 1.65% ± 0.33%
█ █ █
█▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
1.91 s Histogram: frequency by time 3.12 s <
Memory estimate: 307.94 MiB, allocs estimate: 12968641. It looks good to me but if there are still allocations you don't expect let me know and I can have another look. |
OK if its faster than the existing Lanczos then its fine, don't worry about making it faster. |
Yes, I have been benchmarking against Lanczos and it is significantly faster. I think this is ready to be merged if you are happy with the changes. Then I can focus on wrapping up the PR in SemiclassicalOPs as well. |
Don't we want a Or perhaps we just need a more general type: struct GeneralOrthogonalPolynomial{T, WW, XX} <: OrthogonalPolynomial{T}
weight::WW
X::XX
end |
I think the simplicity of the three weight terms in the hierarchy of semiclassical Jacobi means we still want to implement that separately like I have in the PR in that repo since a lot of simplifications happen there for computing the hierarchy step by step. But in general yes I don't see why we can't have a GeneralOrthogonalPolynomial. Raises some design philosophy questions though, since I don't know how to generically "detect" that an arbitrary supplied weight function is the square of a polynomial, so this wouldn't be able to leverage the QR variant? We always could do CholeskyOrthogonalPolynomial and QROrthogonalPolynomial separately but the latter isn't really a nice name. 😅 I think we should definitely keep the Lanzcos approach around, though. It's an extremely helpful sanity check on these things. |
BTW, it would be nice to tag a new version of this for use in SemiclassicalOPs. |
end | ||
|
||
# The generated Jacobi operators are symmetric tridiagonal, so we store their data in cached bands | ||
mutable struct CholeskyJacobiBands{dv,T} <: AbstractCachedVector{T} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait I think we should rename this CholeskyJacobiBand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should take no time at all, should I just open a new PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll just do it
|
||
An optional bool can be supplied, i.e. `cholesky_jacobimatrix(sqrtw, P, false)` to disable checks of symmetry for the weight multiplication matrix and orthonormality for the basis (use with caution). | ||
""" | ||
function cholesky_jacobimatrix(w::Function, P::OrthogonalPolynomial, checks::Bool = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to remove ::OrthogonalPolyomial
as not all orthogonal polynomials are <: OrthogonalPolynomial
, eg, any mapped polynomials
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a sensible replacement? I guess I can just hope the user only gives it sensible input? I can try to compute P's jacobi matrix and then catch an error to return something like "Failed to compute Jacobi matrix, check the supplied Polynomials" or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who cares if the user doesn't give sensible input? It will just error anyways so why even check??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And who's "the user"??? Me??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are the user (for now but others are using this package). 😄 It's nice to get sensible error messages but tbf something like "no method matching jacobimatrix(::Type)" will be fine even for those standards and that will be automatic
You could in theory just check if it is of the form OrthogonalPolynomial(w::AbstractQuasiVector) = OrthogonalPolynomial(w, orthonormalpolynomial(singularities(w)))
OrthogonalPolynomial(w::AbstractQuasiVector, P::AbstractQuasiMatrix) = GeneralOrthogonalPolynomial(w, cholesky_jacobimatrix(w, P)) But this does have a catch: we lose reference to the So perhaps a better option is something like: # represent an Orthogonal polynomial which has a conversion operator to P
struct ConvertedOrthogonalPolynomial{T, WW, XX, PP} <: OrthogonalPolynomial{T}
weight::WW
X::XX
P::PP
end
abstract type AbstractConvertedOPLayout <: AbstractOPLayout end
struct ConvertedOPLayout <: AbstractConvertedOPLayout end
# also change NormalizedOPLayout <: ConvertedOPLayout
# transform to P * U if needed for differentiation, etc.
arguments(::ApplyLayout{typeof(*)}, Q::ConvertedOrthogonalPolynomial) = Q.P, Q.X.U
# also change all the NormalizedOPLayout
@inline copy(L::Ldiv{Lay,<: AbstractConvertedOPLayout}) where Lay = copy(Ldiv{Lay,ApplyLayout{typeof(*)}}(L.A, L.B)) Maybe I'll do the PR for this |
In general better to delete unused code: keeping it around just creates admin, and Git will always keep the code around. Though in this case we are using in the tests. |
Let me fix it first. |
It's up to you! Just assign an issue to me if you want me to do something related to this. I assume that whoever does this new PR will also make the small changes mentioned here, i.e. change "Bands" to "Band" in the QR and Cholesky case and remove the
Remembering this is probably better. we have to carry the conversion operator around either way for expanding the cached values so imo it makes sense to remember the original basis somewhere |
After the unnecessary episode of the premature merger, here is the current status of this PR. Still a lot left to sort out but the functionality is there and works. The old PR with chat history is here.
The big bug to fix for this PR is here: JuliaLinearAlgebra/InfiniteLinearAlgebra.jl#123
Due to that bug I currently have to rely on a very adhoc fix for loss of positive definiteness.
This branch is intended to be used with JuliaApproximation/SemiclassicalOrthogonalPolynomials.jl#70, but both are still WIP.